/**
 * Project Name:payment
 * File Name:SignUtils.java
 * Package Name:cn.swiftpass.utils.payment.sign
 * Date:2014-6-27下午3:22:33
 */

package com.sxkj.common.utils;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;

import static com.sxkj.common.utils.XmlUtils.mapToXml;
import static com.sxkj.common.utils.XmlUtils.xmlToMap;


/**
 * ClassName:SignUtils
 * Function: 签名用的工具箱
 * Date:     2019-12-27 下午3:22:33
 *
 * @author Zhang Xingliang
 */
public class SignUtils {

    public enum SignType {
        MD5, HMACSHA256, SHA1, SHA256
    }

    public static final String FAIL = "FAIL";
    public static final String SUCCESS = "SUCCESS";
    public static final String HMACSHA256 = "HMAC-SHA256";
    public static final String MD5 = "MD5";

    public static final String SHA1 = "SHA1WithRSA";
    public static final String SHA256 = "SHA256WithRSA";

    public static final String FIELD_SIGN = "sign";
    public static final String FIELD_SIGN_TYPE = "sign_type";
    public static final String CHARSET_NAME = "UTF-8";

    /**
     * 过滤参数
     *
     * @param sArray
     * @return
     * @author
     */
    public static Map<String, String> paraFilter(Map<String, String> sArray) {
        Map<String, String> result = new HashMap<String, String>(sArray.size());
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }
        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")) {
                continue;
            }
            result.put(key, value);
        }
        return result;
    }

    /**
     * <将map转成String>
     * <功能详细描述>将map转成String
     *
     * @param payParams
     * @return
     * @see [类、类#方法、类#成员]
     */
    public static String payParamsToString(Map<String, String> payParams) {
        return payParamsToString(payParams, false);
    }

    public static String payParamsToString(Map<String, String> payParams, boolean encoding) {
        return payParamsToString(new StringBuilder(), payParams, encoding);
    }

    /**
     * @param payParams
     * @return
     * @author
     */
    public static String payParamsToString(StringBuilder sb, Map<String, String> payParams, boolean encoding) {
        buildPayParams(sb, payParams, encoding);
        return sb.toString();
    }

    /**
     * @param payParams
     * @return
     * @author
     */
    public static void buildPayParams(StringBuilder sb, Map<String, String> payParams, boolean encoding) {
        List<String> keys = new ArrayList<String>(payParams.keySet());
        Collections.sort(keys);
        for (String key : keys) {
            sb.append(key).append("=");
            if (encoding) {
                sb.append(urlEncode(payParams.get(key)));
            } else {
                sb.append(payParams.get(key));
            }
            sb.append("&");
        }
        sb.setLength(sb.length() - 1);
    }

    public static String urlEncode(String str) {
        try {
            return URLEncoder.encode(str, CHARSET_NAME);
        } catch (Throwable e) {
            return str;
        }
    }
    //######################################  RSA 签名加密 begin #######################################

    //对返回参数的验证签名
    public static boolean checkRsaSign(String sign, String signType, Map<String, String> data
            , Map<String, String> config) throws Exception {
        if ("RSA_1_256".equals(signType)) {
            Map<String, String> Reparams = paraFilter(data);
            StringBuilder Rebuf = new StringBuilder((Reparams.size() + 1) * 10);
            SignUtils.buildPayParams(Rebuf, Reparams, false);
            return checkRsaSign(Rebuf.toString(), sign, "RSA_1_256", config.get("publicKey"));
        } else if ("MD5".equals(signType)) {
            return checkSign(data, config.get("key"));
        }
        return false;
    }

    /**
     * @param preStr
     * @param sign
     * @param signType
     * @param publicKey
     * @return boolean
     * @author Zhang Xingliang
     * @description RSA散列加密签名
     * @date 10:19 2019/12/28
     **/
    public static boolean checkRsaSign(String preStr, String sign, String signType, String publicKey) throws Exception {
        // 调用这个函数前需要先判断是MD5还是RSA
        // 商户的验签函数要同时支持MD5和RSA
        RSAUtil.SignType suite;
        if ("RSA_1_1".equals(signType)) {
            suite = RSAUtil.SignType.SHA1;
        } else if ("RSA_1_256".equals(signType)) {
            suite = RSAUtil.SignType.SHA256;
        } else {
            throw new Exception("不支持的签名方式");
        }

        boolean result = RSAUtil.CheckSign(suite, preStr.getBytes("UTF8"),
                org.apache.commons.codec.binary.Base64.decodeBase64(sign.getBytes("UTF8")),
                publicKey);

        return result;
    }

    //请求时根据不同签名方式去生成不同的sign
    public static String rsaSign(String signType, String preStr, String privateKey, String key) {
        if ("RSA_1_256".equals(signType)) {
            try {
                return rsaSign(preStr, "RSA_1_256", privateKey);
            } catch (Exception e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        } else {
            return Md5Util.sign(preStr, "&key=" + key, "utf-8");
        }
        return null;
    }

    public static String rsaSign(String preStr, String signType, String privateKey) throws Exception {
        RSAUtil.SignType suite;
        if ("RSA_1_1".equals(signType)) {
            suite = RSAUtil.SignType.SHA1;
        } else if ("RSA_1_256".equals(signType)) {
            suite = RSAUtil.SignType.SHA256;
        } else {
            throw new Exception("不支持的签名方式");
        }
        byte[] signBuf = RSAUtil.sign(suite, preStr.getBytes("UTF8"), privateKey);
        return new String(Base64.encodeBase64(signBuf), "UTF8");
    }

    //######################################  RSA 签名加密 end #######################################


    //################################# MD5 和 HMACSHA256 签名加密 begin ###############################

    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 含有sign字段的XML
     */
    public static String signXml(final Map<String, String> data, String key) throws Exception {
        return signXml(data, key, SignType.MD5);
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data     Map类型数据
     * @param key      API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String signXml(final Map<String, String> data, String key, SignType signType) throws Exception {
        String sign = sign(data, key, signType);
        data.put(FIELD_SIGN, sign);
        return mapToXml(data);
    }


    /**
     * 判断签名是否正确
     *
     * @param xmlStr XML格式数据
     * @param key    API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean checkSign(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey(FIELD_SIGN)) {
            return false;
        }
        String sign = data.get(FIELD_SIGN);
        return sign(data, key, SignType.MD5).equals(sign);
    }

    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean checkSign(Map<String, String> data, String key) throws Exception {
        return checkSign(data, key, SignType.MD5);
    }

    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。
     *
     * @param data     Map类型数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean checkSign(Map<String, String> data, String key, SignType signType) throws Exception {
        if (!data.containsKey(FIELD_SIGN)) {
            return false;
        }
        String sign = data.get(FIELD_SIGN);
        return sign(data, key, signType).equals(sign);
    }

    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key  API密钥
     * @return 签名
     */
    public static String sign(final Map<String, String> data, String key) throws Exception {
        return sign(data, key, SignType.MD5);
    }

    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     *
     * @param data     待签名数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String sign(final Map<String, String> data, String key, SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空，则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        } else if (SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        } else if ("RSA_1_1".equals(signType.toString())) {
            return RSAUtil.sign(RSAUtil.SignType.SHA1, sb.toString(), key);
        } else if ("RSA_1_256".equals(signType.toString())) {
            return RSAUtil.sign(RSAUtil.SignType.SHA256, sb.toString(), key);
        } else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }

    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String nonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }

    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance(MD5);
        byte[] array = md.digest(data.getBytes(CHARSET_NAME));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     *
     * @param data 待处理数据
     * @param key  密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        String HmacSHA256 = "HmacSHA256";
        Mac sha256_HMAC = Mac.getInstance(HmacSHA256);
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(CHARSET_NAME), HmacSHA256);
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes(CHARSET_NAME));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    //################################# MD5 和 HMACSHA256 签名加密 end  ###############################
}

